#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>


#define BEEP_NAME  "beep"
#define BEEP_COUNT  1

struct beep_dev {
    dev_t devid;
    int major;
    int minor;

    struct cdev cdev;
    struct class *class;
    struct device *device;

    struct device_node *nd;
    int beep_gpio;
};
struct beep_dev beep;


int led_open (struct inode *node, struct file *filep)
{
    filep->private_data = &beep;
    return 0;
}

ssize_t led_write (struct file *filep, const char __user *buf, size_t size, loff_t *offset)
{
    int ret;
    unsigned char databuf[1];
    struct beep_dev *dev = (struct beep_dev *)filep->private_data;

    ret = __copy_from_user(databuf, buf, 1);
    if(ret < 0)
    {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    if(databuf[0] != 0)  //关闭 beep
    {
        gpio_set_value(dev->beep_gpio, 0);
        printk("close beep!\r\n");
    }
    else  //打开 beep
    {
        gpio_set_value(dev->beep_gpio, 1);
        printk("open beep!\r\n");
    }

    return size;
}

int led_release (struct inode *node, struct file *filep)
{
    struct beep_dev *dev = filep->private_data;
    filep->private_data = NULL;

    return 0;
}

struct file_operations beep_ops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
    .release = led_release,
};

/**
 * 模块入口
 */
static int __init beep_init(void)
{
    int ret;
    printk(" init !\r\n");

    // 获取设备号
    beep.major = 0;
    if(beep.major){
        ret = register_chrdev_region(beep.devid, BEEP_COUNT, BEEP_NAME);
    }
    else{
        ret = alloc_chrdev_region(&beep.devid, 0, BEEP_COUNT, BEEP_NAME);
        beep.major = MAJOR(beep.devid);
        beep.minor = MINOR(beep.devid);
    }
    if(ret < 0){
        goto fail_devid;
    }
    printk("beep .major = %d, beep.minor = %d\r\n", beep.major, beep.minor);

    // 添加设备
    cdev_init(&beep.cdev, &beep_ops);
    ret = cdev_add(&beep.cdev, beep.devid, BEEP_COUNT);
    if(ret < 0){
        goto fail_cdev;
    }

    // 添加类
    beep.class = class_create(THIS_MODULE,BEEP_NAME);
    if(IS_ERR(beep.class)){
        ret = PTR_ERR(beep.class);
        goto fail_class;
    }

    // 添加设备
    beep.device = device_create(beep.class, NULL, beep.devid, NULL,BEEP_NAME);
    if(IS_ERR(beep.device)){
        ret = PTR_ERR(beep.device);
        goto fail_device;
    }


    // 获取设备节点
    beep.nd = of_find_node_by_path("/beep");
    if(beep.nd == NULL){
        printk("Can't find beep node!\r\n");
        ret = -EIO;
        goto fail_node;
    }

    // 获取 beep 所对应的 gpio
    beep.beep_gpio = of_get_named_gpio(beep.nd, "beep-gpios", 0);
    if(beep.beep_gpio < 0){
        ret = -EIO;
        goto fail_gpio;
    }
    printk("beep gpio num = %d\r\n", beep.beep_gpio);

    // 申请 IO
    ret = gpio_request(beep.beep_gpio, "beep-gpios");
    if(ret < 0){
        printk("request beep gpio faild!\r\n");
        return -ENOSYS;
    }

    // 4、使用 IO，设置为输出
    ret = gpio_direction_output(beep.beep_gpio, 0);
    if(ret < 0)
    {
        printk("Set led gpio output faild!\r\n");
        goto fail_setoutput;
    }

    // 5、输出低电平，关闭beep
    gpio_set_value(beep.beep_gpio, 0);


    return 0;

fail_setoutput:
    gpio_free(beep.beep_gpio);
fail_gpio:
fail_node:
fail_device:
    class_destroy(beep.class);
fail_class:
    cdev_del(&beep.cdev);
fail_cdev:
    unregister_chrdev_region(beep.devid, BEEP_COUNT);
fail_devid:
    return ret;
}

/**
 * 模块出口
 */
static void __exit beep_exit(void)
{
    gpio_set_value(beep.beep_gpio, 0);  //  关闭beep
    gpio_free(beep.beep_gpio);
    device_destroy(beep.class, beep.devid);
    class_destroy(beep.class);
    cdev_del(&beep.cdev);
    unregister_chrdev_region(beep.devid, BEEP_COUNT);

    printk(" exit !\r\n");
}

/**
 * 注册模块入口和出口
 */
module_init(beep_init);
module_exit(beep_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("george <g_yubo@163.com>");